///\todo upgrade

//////////////////////////////////////////////////////////////////////////////////
//                                                                              //
//  This file is part of the buola project (https://code.google.com/p/buola/).  //
//                                                                              //
//  Copyright(c) 2007-2012 Xavi Gratal                                          //
//  gratal AT gmail DOT com                                                     //
//                                                                              //
//  Buola is free software: you can redistribute it and/or modify               //
//  it under the terms of the GNU General Public License as published by        //
//  the Free Software Foundation, either version 3 of the License, or           //
//  (at your option) any later version.                                         //
//                                                                              //
//  Buola is distributed in the hope that it will be useful,                    //
//  but WITHOUT ANY WARRANTY; without even the implied warranty of              //
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the               //
//  GNU General Public License for more details.                                //
//                                                                              //
//  You should have received a copy of the GNU General Public License           //
//  along with buola.  If not, see <http://www.gnu.org/licenses/>.              //
//                                                                              //
//////////////////////////////////////////////////////////////////////////////////

#ifndef _BUOLA_IO_CFILE_H_
#define _BUOLA_IO_CFILE_H_

#include <buola/buola.h>

#include <buola/utility/uevent.h>
#include <buola/datetime/clocks.h>
#include <buola/io/curi.h>
#include <buola/io/cfileimpl.h>
#include <buola/os/cuser.h>
#include <buola/os/cgroup.h>
#include <unordered_map>
#include <buola/functors/predicates/binary.h>
#include <buola/io/filesystem.h>
#include <buola/io/cindirectbufferstream.h>

namespace buola { namespace io {

///\addtogroup filesystem
///@{

////////////////////////////////////////////////////////////////////////////

/// file

/// A file is anything which has a name (identified by an URL)
/// and either you can obtain a stream to read from or write data to it,
/// or contains other files (in this case it is also a folder)
///
/// An http address is a file (you can read data from it)
/// A socket is not a file (it has no URL)
///
////////////////////////////////////////////////////////////////////////////

class CFile
{
public:
///\name constructor and destructor
//@{
    ///initializes a null file
    CFile()
    {}
    
    ///initializes the file object with a URI
    explicit CFile(const CURI &pURI);

    ///copy constructor
    CFile(const CFile &pFile)
        :   mImpl(pFile.mImpl)
    {}
    
    CFile(CFile &&pFile)
        :   mImpl(std::move(pFile.mImpl))
    {}
    
    CFile &operator=(const CFile &pFile)
    {
        mImpl=pFile.mImpl;
        return *this;
    }
    
    CFile &operator=(CFile &&pFile)
    {
        mImpl=std::move(pFile.mImpl);
        return *this;
    }

    ///destructor
    ~CFile();
//@}

///\name file system manipulation
//@{
    ///drops any cached information on the file

    ///This causes a subsequent call to any of the functions that return information on the file to obtain
    ///new information.
    void Refresh();

    ///return true if the file exists
    //bool Exists();

    ///removes the file from the filesystem
    void Remove();
    ///renames the file
    void Rename(const std::string&);

    ///moves the file
    void Move(const CURI &pDest);
    ///copies the file
    void Copy(const CURI &pDest,bool=false);

    ///creates the file if it doesn't exist, and updates its modification time if it does
    void Touch();
//@}

///\name file information
//@{
    ///URL of the file
    const CURI& URI() const      {   return mImpl->GetURI();    }
    ///canonical URI
    CURI GetCanonicalURI() const;
    ///if it is a link, the target URL for the link
    CURI GetTarget() const      {   return mImpl->GetTarget();  }

    ///access time
    CCalendarTime AccessTime() const         {   return mImpl->GetAccessTime();          }
    ///modification time
    CCalendarTime CreationTime() const       {   return mImpl->GetCreationTime();        }
    ///creation time
    CCalendarTime ModificationTime() const   {   return mImpl->GetModificationTime();    }

    //! is a link
    bool IsLink() const                     {   return mImpl->GetType()==EFileType::LINK;     }
    //! is a regular file
    bool IsRegular() const                  {   return mImpl->GetType()==EFileType::REGULAR;  }
    //! is a folder
    bool IsFolder() const                   {   return mImpl->GetType()==EFileType::FOLDER;   }
    //! is a character special device
    bool IsChar() const                     {   return mImpl->GetType()==EFileType::CHAR;     }
    //! is a block special device
    bool IsBlock() const                    {   return mImpl->GetType()==EFileType::BLOCK;    }
    //! is a named FIFO
    bool IsFifo() const                     {   return mImpl->GetType()==EFileType::FIFO;     }
    //! is a named socket
    bool IsSocket() const                   {   return mImpl->GetType()==EFileType::SOCKET;   }

    //! size of the file
    ssize_t Size() const                    {   return mImpl->GetSize();                }

    //! user ownership
    os::CUser Owner() const                     {   return mImpl->GetOwner();                 }
    //! group ownership
    os::CGroup Group() const                    {   return mImpl->GetGroup();                 }

    //! permissions
    EPermission Permission() const   {   return mImpl->GetPermission();          }

    ///true if the current user can read the file
    bool CanRead() const              {   return mImpl->CanRead();                }
    ///true if the current user can write to the file
    bool CanWrite() const             {   return mImpl->CanWrite();               }
    ///true if the current user can execute the file
    bool CanExecute() const           {   return mImpl->CanExecute();             }

    ///true if the current user can change ownership of the file
    bool CanChangeOwner() const       {   return mImpl->CanChangeOwner();         }
    ///true if the current user can change group of the file
    bool CanChangeGroup() const       {   return mImpl->CanChangeGroup();         }
    ///true if the current user can change permissions for the file
    bool CanChangePermissions() const {   return mImpl->CanChangePermissions();   }
//@}

    explicit operator bool()        {   return (bool)mImpl; }

///\name related objects
//@{
    ///returns a stream to the file
    PStream GetStream(EMode pMode,EPermission pPermission=EPermission::RW&~get_umask()) const
    {
        return mImpl->GetStream(pMode,pPermission);
    }

    PBufferStream GetBufferStream(EMode pMode,EPermission pPermission=EPermission::RW&~get_umask()) const
    {
        return new CIndirectBufferStream(GetStream(pMode,pPermission));
    }
    
    ///returns the folder where the file is contained
    CFolder GetParent() const;
//@}
    
protected:
    intrusive_ptr<CFileImpl> mImpl;
};

struct AFileNameOrder : public fn::GOrderPredicate
{
    bool operator()(const CFile &p1,const CFile &p2) const
    {
        return p1.URI().Name()<p2.URI().Name();
    }
};

struct AFolderFirstOrder : public fn::GOrderPredicate
{
    bool operator()(const CFile &p1,const CFile &p2) const
    {
        return p1.IsFolder()&&!p2.IsFolder();
    }
};

inline AFileNameOrder file_name_order()
{
    return AFileNameOrder();
}

inline AFolderFirstOrder folder_first_order()
{
    return AFolderFirstOrder();
}

///@}

/*namespace io*/ } /*namespace buola*/ }

#endif
